package com.support.mvc.entity.generator;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;

import java.io.Serializable;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import static com.utils.util.Dates.Pattern.yyyyMMddHHmmssSSS;

/**
 * <pre>
 * 生成 17 位 有序 id，同一毫秒生成的 id 不是绝对有序的
 * 规则： 17位日期(yyyyMMddHHmmssSSS) + 9位随机数字(RandomStringUtils.randomNumeric(9)) + 4位mac地址之和(和不足4位前面补0，超过4位从右侧开始截取4位)
 * 按规则拼接字符串之后包装成 BigInteger 获得 30 位有序的 id
 *
 * 使用参考：在实体类主键 ID 字段使用注解
 * \@GeneratedValue(generator = "bigint30")
 * \@GenericGenerator(name = "bigint30", strategy = "com.support.mvc.entity.generator。Bigint30Generator")
 *  private String id;
 *
 * @author 谢长春 2019/9/17
 */
@Slf4j
public class Bigint30Generator implements IdentifierGenerator {

    /**
     * 随机数长度
     */
    private static final int RANDOM_LENGTH = 9;
    /**
     * 所有 mac 地址的二进制之和，不足4位前面补0，超过4位从右侧开始截取4位
     */
    private static String MAC_SUM = "0000";

    static {
        try {
            int macSum = 0;
            for (Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {
                final NetworkInterface networkInterface = networkInterfaces.nextElement();
                if (networkInterface.isLoopback()
                        || networkInterface.isVirtual()
                        || !networkInterface.isUp()
                        || !isIpV4(networkInterface.getInetAddresses())
                ) {
                    continue;
                }
                final byte[] mac = networkInterface.getHardwareAddress();
                if (mac != null) {
                    final StringBuilder sb = new StringBuilder();
                    for (byte b : mac) {
                        macSum += Byte.toUnsignedLong(b);
                        sb.append(String.format("%02X", b));
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("mac:{} => {} - {}", sb, networkInterface.getName(), networkInterface.getDisplayName());
                    }
                }
            }
            MAC_SUM = StringUtils.right(String.format("%04d", macSum), MAC_SUM.length());
            if (log.isDebugEnabled()) {
                log.debug("MAC_SUM:{}", MAC_SUM);
            }
        } catch (Exception e) {
            log.error("MAC地址获取失败", e);
        }
    }

    /**
     * 判断是否为 ipv4 地址
     *
     * @param address Enumeration<InetAddress>
     * @return true：是，false：否
     */
    public static boolean isIpV4(final Enumeration<InetAddress> address) {
        while (address.hasMoreElements()) {
            InetAddress inetAddress = address.nextElement();
            if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {

    }

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
        return new BigInteger(yyyyMMddHHmmssSSS.now()
                .concat(RandomStringUtils.randomNumeric(RANDOM_LENGTH))
                .concat(MAC_SUM)
        );
    }

    @SneakyThrows
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            System.out.println(i + ":" + new BigInteger(yyyyMMddHHmmssSSS.now() + RandomStringUtils.randomNumeric(9) + MAC_SUM));
        }
        // 测试 10w id生成时间
        System.out.printf("10w 耗时 %d ms%n", System.currentTimeMillis() - start);

        { // 检查重复概率
            Set<BigInteger> ids = new HashSet<>();
            Bigint30Generator generator = new Bigint30Generator();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 10000; j++) {
                        BigInteger id = (BigInteger) generator.generate(null, null);
                        if (ids.contains(id)) {
                            System.err.println(id);
                        } else {
                            ids.add(id);
                        }
                    }
                }).start();
            }
        }
    }
}
